<template>
  <div :id="id" />
</template>
<script>
import * as d3 from 'd3'

export default {
  props: {
    data: Object,
    nodeWidth: {
      type: Number,
      default: 160
    },
    nodeHeight: {
      type: Number,
      default: 40
    },
    active: {
      type: String,
      default: ''
    }
  },
  data() {
    return {
      id: 'TreeMap' + randomString(4),
      deep: 0,
      treeData: null,
      show: true,
      demoData: {
        'label': '后台admin前端',
        link: '',
        url: '',
        'children': [{
          'label': '首页',
          url: '/index'
        },
        {
          'label': '玩家列表',
          url: '#/player_list/index'
        },
        {
          'label': '房间设置',
          url: '#/permission/page',
          'children': [{
            'label': '基础设置',
            url: '#/permission/page'
          },
          {
            'label': '游戏开关设置',
            url: '#/permission/directive'
          },
          {
            'label': '操作日志',
            url: '#/permission/role'
          }
          ]
        },
        {
          'label': '开奖数据',
          url: '#/documentation/index'
        },
        {
          'label': '财务报表',
          url: '#/financial/index',
          'children': [{
            'label': '上下分查询',
            url: '#/financial/index'
          },
          {
            'label': '财务统计报表',
            url: '#/financial/baobiao'
          },
          {
            'label': '玩家历史下注信息',
            url: '#/financial/xiazhu'
          },
          {
            'label': '玩家历史积分变动',
            url: '#/financial/jifen'
          },
          {
            'label': '回水历史查询',
            url: '#/financial/huishui'
          }
          ]
        },
        {
          'label': '赔率设置',
          url: '#/odds',
          'children': [{
            'label': 'PC类赔率设置',
            url: '#/odds/type_28'
          },
          {
            'label': '赛车类赔率设置',
            url: '#/odds/pk10'
          },
          {
            'label': '快三类赔率设置',
            url: '#/odds/k3'
          },
          {
            'label': '时时彩类赔率设置',
            url: '#/odds/ssc'
          }
		  // ,
    //       {
    //         'label': '翻摊类型',
    //         url: '#/odds/ft'
    //       }
          ]
        },
        {
          'label': '回水助手',
          url: '#/backwater/index',
          'children': [{
            'label': '回水设置',
            url: '#/backwater/index'
          },
          {
            'label': '回水计算',
            url: '#/backwater/jisuan'
          },
          {
            'label': '回水查询',
            url: '#/backwater/select'
          },
          {
            'label': '代理返点设置',
            url: '#/backwater/fandian_set'
          },
          {
            'label': '代理返点计算',
            url: '#/backwater/fandian_jisuan'
          },
          {
            'label': '月统计报表',
            url: '#/backwater/baobiao'
          },
          {
            'label': '代理月统计报表',
            url: '#/backwater/daili_baobiao'
          }
          ]
        },
        {
          'label': '消息设置',
          url: '#/message/index',
          'children': [{
            'label': '游戏消息设置',
            url: '#/message/index'
          }]
        },
        {
          'label': '智能自动托',
          url: '#/ai/index'
        }
        ]
      }
    }
  },
  mounted() {
    this.$nextTick(
      () => {
        this.drawMap()
      }
    )
  },
  methods: {
    drawMap() {
      const that = this
      // 源数据
      let data = {}
      // 判断data是否为空对象
      if (this.data && JSON.stringify(this.data) !== '{}') {
        data = this.data
      } else {
        data = this.demoData
      }
      if (!this.treeData) {
        this.treeData = data
      } else {
        // 清空画布
        d3.select('#' + this.id).selectAll('svg').remove()
      }
      const leafList = []
      getTreeLeaf(data, leafList)
      const leafNum = leafList.length
      const TreeDeep = getDepth(data)
      // 左右内边距
      const mapPaddingLR = 10
      // 上下内边距
      const mapPaddingTB = 0
      const mapWidth = this.nodeWidth * TreeDeep + mapPaddingLR * 2
      const mapHeight = (this.nodeHeight - 4) * leafNum + mapPaddingTB * 2
      // 定义画布—— 外边距 10px
      const svgMap = d3.select('#' + this.id).append('svg').attr('width', mapWidth).attr('height',
        mapHeight)
        .style('margin', '0px')
      // 定义树状图画布
      const treeMap = svgMap.append('g').attr('transform', 'translate(' + mapPaddingLR + ',' + (
        mapHeight / 2 -
					mapPaddingTB) + ')')
      // 将源数据转换为可以生成树状图的数据(有节点 nodes 和连线 links )
      const treeData = d3.tree()
      // 设置每个节点的尺寸
        .nodeSize(
          // 节点包含后方的连接线 [节点高度，节点宽度]
          [this.nodeHeight, this.nodeWidth]
        )
      // 设置树状图节点之间的垂直间隔
        .separation(function(a, b) {
          // 样式一：节点间等间距
          // return (a.parent == b.parent ? 1: 2) / a.depth;
          // 样式二：根据节点子节点的数量，动态调整节点间的间距
          let rate = (a.parent == b.parent ? (b.children ? b.children.length / 2 : 1) : 2) / a
            .depth
          // 间距比例不能小于0.7，避免间距太小而重叠
          if (rate < 0.7) {
            rate = 0.7
          }
          return rate
        })(
          // 创建层级布局，对源数据进行数据转换
          d3.hierarchy(data).sum(function(node) {
            // 函数执行的次数，为树节点的总数,node为每个节点
            return node.value
          })
        )
      // 贝塞尔曲线生成器
      const Bézier_curve_generator = d3.linkHorizontal()
        .x(function(d) {
          return d.y
        })
        .y(function(d) {
          return d.x
        })
      // 绘制边
      treeMap.selectAll('path')
      // 节点的关系 links
        .data(treeData.links())
        .enter()
        .append('path')
        .attr('d', function(d) {
          // 根据name值的长度调整连线的起点
          var start = {
            x: d.source.x,
            // 连线起点的x坐标
            // 第1个10为与红圆圈的间距，第2个10为link内文字与边框的间距，第3个10为标签文字与连线起点的间距
            y: d.source.y + 10 + (d.source.data.link ? (getPXwidth(d.source.data.link) +
									10) : 0) +
								getPXwidth(d.source.data.label) + 10
          }
          var end = {
            x: d.target.x,
            y: d.target.y
          }
          return Bézier_curve_generator({
            source: start,
            target: end
          })
        })
        .attr('fill', 'none')
        .attr('stroke', '#c3c3c3')
      // 虚线
      // .attr("stroke-dasharray", "8")
        .attr('stroke-width', 1)
      // 创建分组——节点+文字
      const groups = treeMap.selectAll('g')
      // 节点 nodes
        .data(treeData.descendants())
        .enter()
        .append('g')
        .attr('transform', function(d) {
          var cx = d.x
          var cy = d.y
          return 'translate(' + cy + ',' + cx + ')'
        })
      // 绘制节点(节点前的圆圈)
      groups.append('circle')
      // 树的展开折叠
        .on('click', function(event, node) {
          const data = node.data
          if (data.children) {
            data.childrenTemp = data.children
            data.children = null
          } else {
            data.children = data.childrenTemp
            data.childrenTemp = null
          }
          that.drawMap()
        })
        .attr('cursor', 'pointer')
        .attr('r', 4)
        .attr('fill', function(d) {
          if (d.data.childrenTemp) {
            return 'red'
          } else {
            return 'white'
          }
        })
        .attr('stroke', 'red')
        .attr('stroke-width', 1)
      // 绘制标注(节点前的矩形)
      groups.append('rect')
        .attr('x', 8)
        .attr('y', -10)
        .attr('width',
          function(d) {
            return d.data.link ? (getPXwidth(d.data.link) + 10) : 0
          }
        )
        .attr('height', 22)
        .attr('fill', 'grey')
      // 添加圆角
        .attr('rx', 4)
      // 绘制链接方式
      groups.append('text')
        .attr('x', 12)
        .attr('y', -5)
        .attr('dy', 10)
        .attr('fill', 'white')
        .attr('font-size', 12)
        .text(function(d) {
          return d.data.link
        })
      // 绘制文字
      groups.append('text')
        .on('click', function(event, node) {
          const data = node.data
          // 被禁用的节点，点击无效
          if (data.disabled) {
            return
          }
          // 有外链的节点，打开新窗口后恢复到思维导图页面
          if (data.url) {
            // window.open(data.url)
            window.location.href = data.url
            that.$emit('activeChange', 'map')
            return
          }
          // 标准节点—— 传出 prop
          if (data.dicType) {
            that.$emit('dicTypeChange', data.dicType)
          }
          // 标准节点—— 传出 prop
          if (data.prop) {
            that.$emit('activeChange', data.prop)
          }
        })
        .attr('x', function(d) {
          return 12 + (d.data.link ? (getPXwidth(d.data.link) + 10) : 0)
        })
        .attr('fill',
          function(d) {
            if (d.data.prop === that.active) {
              return '#409EFF'
            }
          }
        )
        .attr('font-weight',
          function(d) {
            if (d.data.prop === that.active) {
              return 'bold'
            }
          })
        .attr('font-size', 14)
        .attr('cursor',
          function(d) {
            if (d.data.disabled) {
              return 'not-allowed'
            } else {
              return 'pointer'
            }
          })
        .attr('y', -5)
        .attr('dy', 10)
        .attr('slot', function(d) {
          return d.data.prop
        })
        .text(function(d) {
          return d.data.label
        })
    }
  }
}

// 获取树的深度
function getDepth(json) {
  var arr = []
  arr.push(json)
  var depth = 0
  while (arr.length > 0) {
    var temp = []
    for (var i = 0; i < arr.length; i++) {
      temp.push(arr[i])
    }
    arr = []
    for (var i = 0; i < temp.length; i++) {
      if (temp[i].children && temp[i].children.length > 0) {
        for (var j = 0; j < temp[i].children.length; j++) {
          arr.push(temp[i].children[j])
        }
      }
    }
    if (arr.length >= 0) {
      depth++
    }
  }
  return depth
}

// 提取树的子节点，最终所有树的子节点都会存入传入的leafList数组中
function getTreeLeaf(treeData, leafList) {
  // 判断是否为数组
  if (Array.isArray(treeData)) {
    treeData.forEach(item => {
      if (item.children && item.children.length > 0) {
        getTreeLeaf(item.children, leafList)
      } else {
        leafList.push(item)
      }
    })
  } else {
    if (treeData.children && treeData.children.length > 0) {
      getTreeLeaf(treeData.children, leafList)
    } else {
      leafList.push(treeData)
    }
  }
}

// 获取包含汉字的字符串的长度
function getStringSizeLength(string) {
  // 先把中文替换成两个字节的英文，再计算长度
  return string.replace(/[\u0391-\uFFE5]/g, 'aa').length
}

// 生成随机的字符串
function randomString(strLength) {
  strLength = strLength || 32
  const strLib = 'ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz'
  let n = ''
  for (let i = 0; i < strLength; i++) {
    n += strLib.charAt(Math.floor(Math.random() * strLib.length))
  }
  return n
}

// 获取字符串的像素宽度
function getPXwidth(str, fontSize = '12px', fontFamily = 'Microsoft YaHei') {
  var span = document.createElement('span')
  var result = {}
  result.width = span.offsetWidth
  result.height = span.offsetHeight
  span.style.visibility = 'hidden'
  span.style.fontSize = fontSize
  span.style.fontFamily = fontFamily
  span.style.display = 'inline-block'
  document.body.appendChild(span)
  if (typeof span.textContent !== 'undefined') {
    span.textContent = str
  } else {
    span.innerText = str
  }
  result.width = parseFloat(window.getComputedStyle(span).width) - result.width
  // 字符串的显示高度
  // result.height = parseFloat(window.getComputedStyle(span).height) - result.height;
  return result.width
}
</script>
